﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

using Model.Hero;
using RemotingInterfaces;

namespace Model.Adventure
{
    //rules for combat in an encounter
    public class Combat
    {
        //attack and defense ratings are scaled using the scaling factor
        //damage is multiplied using the damage multiplier
        private double _scaling_factor = 3.5, _damage_multiplier = 20;
        //damage from critical hits are increased by this factor:
        private double _critical_hit_damage_multiplier = 2.0;

        private Random rand;
        private ManualResetEvent _MRE;
        private int _round;

        public ManualResetEvent MRE
        {
            get { return _MRE; }
        }        

        public Combat()
        {
            _MRE = new ManualResetEvent(false);
            _round = 0;
        }

        public int Round
        {
            get { return _round; }
        }

        public void RoundOfCombat(Character attacker, Character defender)
        {
            rand = new Random();            
           
            //Console.WriteLine("***defender current health = {0}***", defender.CurrentHealth);

            //sort any hits before combat     
            Console.WriteLine("First strikes...");
            FirstStrikes(attacker, defender);
            Console.WriteLine("DamageCalculator...");
            DamageCalculator(attacker, defender);           
            
            if (defender.CurrentHealth > 0)
            {
                //if alive, attacks back
                DamageCalculator(defender, attacker);        
            }
            else
                Console.WriteLine(defender.Name + " died!");

            if (attacker.CurrentHealth == 0)
                Console.WriteLine(attacker.Name + " died!");            
        }

        //
        public void Battle(Character Offender, Character Defender)
        {
            RoundOfCombat(Offender, Defender);
            //Console.WriteLine("Starting battle");
            //_round++;
            //bool go = true;
            //_Combat: while (go)
            //{
                //Console.WriteLine("Round #" + _round);
                //foreach (Character Offender in Offenders) //heroes
                //{                   
                    //get a primary target if old one is either non-existant or dead
                    //while (Offender.PrimaryTarget == null || Offender.PrimaryTarget.CurrentHealth == 0)
                    //{
                        //check to see if all monsters are dead
                        /*var alive = from defender in Defenders
                                    where defender.CurrentHealth > 0
                                    select defender;//*/
                        //if (Defenders.Count(D => D.CurrentHealth > 0) == 0)
                        //    return;
                        //Console.WriteLine("Waiting for primary target from client");                                                
//                        _MRE.WaitOne();

                        //_primary_target = (Offender as Hero.Hero).PrimaryTarget;
                        
                        //if (_primary_target.CurrentHealth <= 0)
                        //    _primary_target = null;
                        //Console.WriteLine("Primary target accepted");
  //                  }                    
            
                    //_primary_target = null;
                    //if (round > 8) return;
                //}//end foreach
                //all heroes have had their turn
                /*foreach (Character Defender in Defenders)
                { 
                    //assuming monster for now. could be heroes if PvP
                    //pick random target among (live) offenders                    
                    Character target = Offenders.ElementAt(rand.Next(Offenders.Count(O => O.CurrentHealth > 0)));
                    if (target.CurrentHealth == 0)
                        return;
                    RoundOfCombat(Defender, target);
                }*/
                //increment cooldown counters
                /*foreach (Character Offender in Offenders)
                    if (Offender.CurrentHealth > 0) Offender.Increment_Cooldown_Counters();
                foreach (Character Defender in Defenders)
                    if (Defender.CurrentHealth > 0) Defender.Increment_Cooldown_Counters();                *
                 * /
                //look for live monster
            /*
                foreach (Character _mob in Defenders)
                {
                    //alive monster found. continue combat and stop searching
                    if (_mob.CurrentHealth > 0)
                    {
                        //continue Combat;
                        goto _Combat;                  
                    }
                } //end foreach
                break;
            }//end while*/
        }
       
        private void DamageCalculator(Character Attacker, Character Defender)
        {
            int Attacker_Attack_Rating = Attacker.Attack_Rating, Defender_Defense_Rating = Defender.Defense_Rating;
            //Console.WriteLine("{0}", damage_multiplier * (double)(scaling_factor * Attacker_Attack_Rating + Defender_Defense_Rating) / (double)(Attacker_Attack_Rating + scaling_factor * Defender_Defense_Rating));
            
            double dam = _damage_multiplier * (double)(_scaling_factor * Attacker_Attack_Rating + Defender_Defense_Rating) / (double)(Attacker_Attack_Rating + _scaling_factor * Defender_Defense_Rating);
            //check for critical hit        
            //Console.WriteLine("{0} critical hit chance = {1}%", Attacker.Name, Attacker.Critical_Hit_Chance * 100);
            //bool crit = false;
            if (rand.NextDouble() <= Attacker.Critical_Hit_Chance)
            {
                dam = dam * _critical_hit_damage_multiplier;
                //crit = true;
                Console.WriteLine("{0} scored a critical hit", Attacker.Name);
            }

            //apply some randomness
            dam = dam * (1 - rand.NextDouble() / 5);
            
            Attacker.Offensive_Damage_Stats.Base_Damage = dam;            

            //adjust damage
            int total_dam = 0;
            double slash_dam = 0, pierc_dam = 0, bludg_dam = 0, fire_dam = 0, electrical_dam = 0, ice_dam = 0, acid_dam = 0,
                sonic_dam = 0, negative_energy_dam = 0, positive_energy_dam = 0, divine_dam = 0, arcane_dam = 0,
                poison_dam = 0;
            double slash_res, pierc_res, bludg_res, fire_res, electrical_res, ice_res, acid_res, sonic_res,
                negative_energy_res, positive_energy_res, divine_res, arcane_res, poison_res;
            double slash_res_penalty = 0, pierc_res_penalty = 0, bludg_res_penalty = 0, fire_res_penalty = 0,
                electrical_res_penalty = 0, ice_res_penalty = 0, acid_res_penalty = 0, sonic_res_penalty = 0,
                negative_energy_res_penalty = 0, positive_energy_res_penalty = 0, divine_res_penalty = 0,
                arcane_res_penalty = 0, poison_res_penalty = 0;

            //this is the time to look for damage resistance lowering stats/spells etc - all must be found in debuffs/buffs

            //only compute the damage if there is actually anything to compute
            if (Attacker.Offensive_Damage_Stats.Slashing > 0 || Attacker.Offensive_Damage_Stats.Slashing_Damage_Bonus > 0)
            {
                //defenders damage resistance - any penalties incurred by feats, buffs, debuffs etc.
                slash_res = Defender.Defensive_Damage_Stats.Slashing - slash_res_penalty;
                //capped at 100%
                slash_res = slash_res > 1 ? 1 : slash_res;
                //add flat bonusses before any damage reduction
                slash_dam = (Attacker.Offensive_Damage_Stats.Slashing_Damage + Attacker.Offensive_Damage_Stats.Slashing_Damage_Bonus - Defender.Defensive_Damage_Stats.Slashing_Damage_Bonus) * (1 - slash_res);
                //dam = max(dam, 0)
                slash_dam = slash_dam < 0 ? 0 : slash_dam;                
            }
            if (Attacker.Offensive_Damage_Stats.Piercing > 0 || Attacker.Offensive_Damage_Stats.Piercing_Damage_Bonus > 0)
            {
                pierc_res = Defender.Defensive_Damage_Stats.Piercing - pierc_res_penalty;
                pierc_res = pierc_res > 1 ? 1 : pierc_res;
                pierc_dam = (Attacker.Offensive_Damage_Stats.Piercing_Damage + Attacker.Offensive_Damage_Stats.Piercing_Damage_Bonus - Defender.Defensive_Damage_Stats.Piercing_Damage_Bonus) * (1 - pierc_res);
                pierc_dam = pierc_dam < 0 ? 0 : pierc_dam;
            }
            if (Attacker.Offensive_Damage_Stats.Bludgeoning > 0 || Attacker.Offensive_Damage_Stats.Bludgeoning_Damage_Bonus > 0)
            {
                bludg_res = Defender.Defensive_Damage_Stats.Bludgeoning - bludg_res_penalty;
                bludg_res = bludg_res > 1 ? 1 : bludg_res;
                bludg_dam = (Attacker.Offensive_Damage_Stats.Bludgeoning_Damage + Attacker.Offensive_Damage_Stats.Bludgeoning_Damage_Bonus - Defender.Defensive_Damage_Stats.Bludgeoning_Damage_Bonus)* (1 - bludg_res);
                bludg_dam = bludg_dam < 0 ? 0 : bludg_dam;
            }
            if (Attacker.Offensive_Damage_Stats.Fire > 0 || Attacker.Offensive_Damage_Stats.Fire_Damage_Bonus > 0)
            {
                fire_res = Defender.Defensive_Damage_Stats.Fire - fire_res_penalty;
                fire_res = fire_res > 1 ? 1 : fire_res;
                fire_dam = (Attacker.Offensive_Damage_Stats.Fire_Damage + Attacker.Offensive_Damage_Stats.Fire_Damage_Bonus - Defender.Defensive_Damage_Stats.Fire_Damage_Bonus) * (1 - fire_res);
                fire_dam = fire_dam < 0 ? 0 : fire_dam;
            }
            if (Attacker.Offensive_Damage_Stats.Electrical > 0 || Attacker.Offensive_Damage_Stats.Elecrical_Damage_Bonus > 0)
            {
                electrical_res = Defender.Defensive_Damage_Stats.Electrical - electrical_res_penalty;
                electrical_res = electrical_res > 1 ? 1 : electrical_res;
                electrical_dam = (Attacker.Offensive_Damage_Stats.Electrical_Damage + Attacker.Offensive_Damage_Stats.Elecrical_Damage_Bonus - Defender.Defensive_Damage_Stats.Elecrical_Damage_Bonus) * (1 - electrical_res);
                electrical_dam = electrical_dam > 0 ? 0 : electrical_dam;
            }
            if (Attacker.Offensive_Damage_Stats.Ice > 0 || Attacker.Offensive_Damage_Stats.Ice_Damage_Bonus > 0)
            {
                ice_res = Defender.Defensive_Damage_Stats.Ice - ice_res_penalty;
                ice_res = ice_res > 1 ? 1 : ice_res;
                ice_dam = (Attacker.Offensive_Damage_Stats.Ice_Damage + Attacker.Offensive_Damage_Stats.Ice_Damage_Bonus - Defender.Defensive_Damage_Stats.Ice_Damage_Bonus) * (1 - ice_res);
                ice_dam = ice_dam < 0 ? 0 : ice_dam;
            }
            if (Attacker.Offensive_Damage_Stats.Acid > 0 || Attacker.Offensive_Damage_Stats.Acid_Damage_Bonus > 0)
            {
                acid_res = Defender.Defensive_Damage_Stats.Acid - acid_res_penalty;
                acid_res = acid_res > 1 ? 1 : acid_res;
                acid_dam = (Attacker.Offensive_Damage_Stats.Acid_Damage + Attacker.Offensive_Damage_Stats.Acid_Damage_Bonus - Defender.Defensive_Damage_Stats.Acid_Damage_Bonus) * (1 - acid_res);
                acid_dam = acid_dam < 0 ? 0 : acid_dam;
            }
            if (Attacker.Offensive_Damage_Stats.Sonic > 0 || Attacker.Offensive_Damage_Stats.Sonic_Damage_Bonus > 0)
            {
                sonic_res = Defender.Defensive_Damage_Stats.Sonic - sonic_res_penalty;
                sonic_res = sonic_res > 1 ? 1 : sonic_res;
                sonic_dam = (Attacker.Offensive_Damage_Stats.Sonic_Damage + Attacker.Offensive_Damage_Stats.Sonic_Damage_Bonus - Defender.Defensive_Damage_Stats.Sonic_Damage_Bonus) * (1 - sonic_res);
                sonic_dam = sonic_dam < 0 ? 0 : sonic_dam;
            }
            if (Attacker.Offensive_Damage_Stats.Negative_Energy > 0 || Attacker.Offensive_Damage_Stats.Negative_Energy_Damage_Bonus > 0)
            {
                negative_energy_res = Defender.Defensive_Damage_Stats.Negative_Energy - negative_energy_res_penalty;
                negative_energy_res = negative_energy_res > 1 ? 1 : negative_energy_res;
                negative_energy_dam = (Attacker.Offensive_Damage_Stats.Negative_Energy_Damage + Attacker.Offensive_Damage_Stats.Negative_Energy_Damage_Bonus - Defender.Defensive_Damage_Stats.Negative_Energy_Damage_Bonus) * (1 - negative_energy_res);
                negative_energy_dam = negative_energy_dam < 0 ? 0 : negative_energy_dam;
            }
            if (Attacker.Offensive_Damage_Stats.Positive_Energy > 0 || Attacker.Offensive_Damage_Stats.Positive_Energy_Damage_Bonus > 0)
            {
                positive_energy_res = Defender.Defensive_Damage_Stats.Positive_Energy - positive_energy_res_penalty;
                positive_energy_res = positive_energy_res > 1 ? 1 : positive_energy_res;
                positive_energy_dam = (Attacker.Offensive_Damage_Stats.Positive_Energy_Damage + Attacker.Offensive_Damage_Stats.Positive_Energy_Damage_Bonus - Defender.Defensive_Damage_Stats.Positive_Energy_Damage_Bonus) * (1 - positive_energy_res);
                positive_energy_dam = positive_energy_dam < 0 ? 0 : positive_energy_dam;
            }
            if (Attacker.Offensive_Damage_Stats.Divine > 0 || Attacker.Offensive_Damage_Stats.Divine_Damage_Bonus > 0)
            {
                divine_res = Defender.Defensive_Damage_Stats.Divine - divine_res_penalty;
                divine_res = divine_res > 1 ? 1 : divine_res;
                divine_dam = (Attacker.Offensive_Damage_Stats.Divine_Damage + Attacker.Offensive_Damage_Stats.Divine_Damage_Bonus - Defender.Defensive_Damage_Stats.Divine_Damage_Bonus) * (1 - divine_res);
                divine_dam = divine_dam < 0 ? 0 : divine_dam;
            }
            if (Attacker.Offensive_Damage_Stats.Arcane > 0 || Attacker.Offensive_Damage_Stats.Arcane_Damage_Bonus > 0)
            {
                arcane_res = Defender.Defensive_Damage_Stats.Arcane - arcane_res_penalty;
                arcane_res = arcane_res > 1 ? 1 : arcane_res;
                arcane_dam = (Attacker.Offensive_Damage_Stats.Arcane_Damage + Attacker.Offensive_Damage_Stats.Arcane_Damage_Bonus - Defender.Defensive_Damage_Stats.Arcane_Damage_Bonus) * (1 - arcane_res);
                arcane_dam = arcane_dam < 0 ? 0 : arcane_dam;
            }
            if (Attacker.Offensive_Damage_Stats.Acid > 0 || Attacker.Offensive_Damage_Stats.Acid_Damage_Bonus > 0)
            {
                acid_res = Defender.Defensive_Damage_Stats.Acid - acid_res_penalty;
                acid_res = acid_res > 1 ? 1 : acid_res;
                acid_dam = (Attacker.Offensive_Damage_Stats.Acid_Damage + Attacker.Offensive_Damage_Stats.Acid_Damage_Bonus - Defender.Defensive_Damage_Stats.Acid_Damage_Bonus) * (1 - acid_res);
                acid_dam = acid_dam < 0 ? 0 : acid_dam;
            }
            if (Attacker.Offensive_Damage_Stats.Poison > 0 || Attacker.Offensive_Damage_Stats.Poison_Damage_Bonus > 0)
            {
                poison_res = Defender.Defensive_Damage_Stats.Poison - poison_res_penalty;
                poison_res = poison_res > 1 ? 1 : poison_res;
                poison_dam = (Attacker.Offensive_Damage_Stats.Poison_Damage + Attacker.Offensive_Damage_Stats.Poison_Damage_Bonus - Defender.Defensive_Damage_Stats.Poison_Damage_Bonus) * (1 - poison_res);
                poison_dam = poison_dam < 0 ? 0 : poison_dam;
            }

            //any resistances == 1 should produce a combat message telling that the receiver was immune to <type> damage

            total_dam = (int)(slash_dam + pierc_dam + bludg_dam + fire_dam + electrical_dam + ice_dam + acid_dam +
                sonic_dam + negative_energy_dam + positive_energy_dam + divine_dam + arcane_dam +
                poison_dam);
            Defender.CurrentHealth -= total_dam;

            //
            Console.WriteLine("{0} attacks {1} dealing {2} damage", Attacker.Name, Defender.Name, total_dam);
        } 

        private void FirstStrikes(Character Attacker, Character Defender)
        {            
            //int damage;
            var first_strike_feats = from feat in Attacker.Feats
                                     where feat.First_Strike > 0 && feat.Ready
                                     orderby feat.First_Strike
                                     select feat;
            //foreach (Feat f in first_strike_feats)
            //Console.WriteLine("Found feat {0} matching first strike criteria", f.Name);

            //check for riposte feats in defender (if there was any first strike feats for hero)            
            IEnumerable<Feat> riposte_feats = null;
            if (first_strike_feats.Count() > 0)
            {         
                riposte_feats = from feat in Defender.Feats
                                where feat.Riposte > 0 && feat.Ready
                                orderby feat.Riposte
                                select feat;

                //foreach (Feat f in riposte_feats)
                //    Console.WriteLine(f.Name + " (Riposte value) = " + f.Riposte);
            }
            //can use several first strike feats
            foreach (Feat A in first_strike_feats) //A for attacker
            {
                //Console.WriteLine("Feat: {0} First Strike Rating = {1}", strikes.Name, strikes.First_Strike);
                Console.WriteLine(Attacker.Name + " gains a first strike on " + Defender.Name);
                DamageCalculator(Attacker, Defender);
                //Defender.Deal_Damage(new Damage(damage));
                //ApplyDamage(Defender, Attacker.Damage_Stats, damage);
                A.Activate();
                //Console.WriteLine("First strike is {0}", A.Ready ? "Ready" : "Not ready");
                //Console.WriteLine(Attacker.Name + " gains a first strike on " + Defender.Name + " dealing " + damage + " damage");
                if (Defender.CurrentHealth > 0)
                {
                    //counter highest value first strike with highest value riposte
                    foreach (Feat D in riposte_feats) //D for defender
                    {
                        if (!D.Ready)
                            continue;

                        double chance = 0.25 * (double)(5 * D.Riposte + A.First_Strike) / (double)(5 * A.First_Strike + D.Riposte);
                        //activate feat
                        D.Activate();                        
                        //Console.WriteLine("chance for {1} < {0}%", 100 * chance, D.Name);                        

                        if (rand.NextDouble() < chance)
                        {
                            //riposte successfull
                            Console.WriteLine(Defender.Name + " deflects attack from " + Attacker.Name);
                            DamageCalculator(Defender, Attacker);
                            //Attacker.Deal_Damage(new Damage(damage));
                            //ApplyDamage(Attacker, Defender.Damage_Stats, damage);
                            
                            if(Attacker.CurrentHealth <= 0)
                                Console.WriteLine(Attacker.Name + " died!");
                            break;
                        }
                    }//end foreach
                }//end if
                else
                    Console.WriteLine(Defender.Name + " died!");
            }
        }        
    }
}
